/*===========================================================================
  Copyright (C) 2014 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  This library is free software; you can redistribute it and/or modify it 
  under the terms of the GNU Lesser General Public License as published by 
  the Free Software Foundation; either version 2.1 of the License, or (at 
  your option) any later version.

  This library is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License 
  along with this library; if not, write to the Free Software Foundation, 
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

  See also the full LGPL text here: http://www.gnu.org/copyleft/lesser.html
===========================================================================*/

package net.sf.okapi.applications.lynx;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Stack;

import net.sf.okapi.lib.xliff2.core.Fragment;
import net.sf.okapi.lib.xliff2.core.MTag;
import net.sf.okapi.lib.xliff2.core.Part;
import net.sf.okapi.lib.xliff2.core.StartFileData;
import net.sf.okapi.lib.xliff2.core.StartGroupData;
import net.sf.okapi.lib.xliff2.core.Unit;
import net.sf.okapi.lib.xliff2.its.AllowedCharacters;
import net.sf.okapi.lib.xliff2.its.DataCategories;
import net.sf.okapi.lib.xliff2.its.Domain;
import net.sf.okapi.lib.xliff2.its.IITSItem;
import net.sf.okapi.lib.xliff2.its.ITSItems;
import net.sf.okapi.lib.xliff2.its.IWithITSAttributes;
import net.sf.okapi.lib.xliff2.its.MTConfidence;
import net.sf.okapi.lib.xliff2.its.TextAnalysis;
import net.sf.okapi.lib.xliff2.reader.Event;
import net.sf.okapi.lib.xliff2.reader.URIContext;
import net.sf.okapi.lib.xliff2.reader.XLIFFReader;

public class ITSTest {
	
	private boolean consoleOutput;
	private boolean showTrace;
	private String dc;
	private PrintWriter writer;
	private String xpath;
	private int fileCount;
	private Stack<Integer> groupCounts;
	private Stack<Integer> unitCounts;
	
	private class Info {
		public String id;
		public boolean trans;
		public boolean pws;

		public Info (String id,
			boolean trans,
			boolean pws)
		{
			this.id = id;
			this.trans = trans;
			this.pws = pws;
		}
	}

	public ITSTest (boolean consoleOutput,
		boolean showTrace,
		String dc)
	{
		this.consoleOutput = consoleOutput;
		this.showTrace = showTrace;
		this.dc = dc;
	}
	
	private boolean isDCValid () {
		switch ( dc ) {
		case DataCategories.MTCONFIDENCE:
		case DataCategories.DOMAIN:
		case DataCategories.ALLOWEDCHARACTERS:
		case DataCategories.TEXTANALYSIS:
		case DataCategories.TRANSLATE:
		case DataCategories.PRESERVESPACE:
			return true;
		}
		// Else: bad argument
		System.out.print(String.format("Invalid data category value '%s'.\n", dc));
		System.out.print("The data category values supported are:\n");
		System.out.print("  "+DataCategories.MTCONFIDENCE+"\n");
		System.out.print("  "+DataCategories.DOMAIN+"\n");
		System.out.print("  "+DataCategories.ALLOWEDCHARACTERS+"\n");
		System.out.print("  "+DataCategories.TEXTANALYSIS+"\n");
		System.out.print("  "+DataCategories.TRANSLATE+"\n");
		System.out.print("  "+DataCategories.PRESERVESPACE+"\n");
		return false;
	}
	
	public void process (List<File> inputFiles) {
		if ( !isDCValid() ) return;
		System.out.println("Data Category: "+dc);
		try ( XLIFFReader reader = new XLIFFReader() ) {
			for ( File file : inputFiles ) {
				try {
					System.out.println("Input: "+file.getAbsolutePath());
					reader.open(file);
					openOutput(file);
					fileCount = 0;
					while ( reader.hasNext() ) {
						Event event = reader.next();
						switch ( event.getType() ) {
						case START_XLIFF:
							xpath += "/xliff[1]";
							break;
						case START_FILE:
							xpath += "/file["+(++fileCount)+"]";
							groupCounts = new Stack<>(); groupCounts.push(0);
							unitCounts = new Stack<>(); unitCounts.push(0);
							break;
						case START_GROUP:
							groupCounts.push(groupCounts.pop()+1);
							xpath += "/group["+groupCounts.peek()+"]";
							groupCounts.push(0);
							unitCounts.push(0);
							break;
						case TEXT_UNIT:
							unitCounts.push(unitCounts.pop()+1);
							xpath += "/unit["+unitCounts.peek()+"]";
							break;
						case END_GROUP:
							unitCounts.pop();
							groupCounts.pop();
							// Fall thru
						case END_XLIFF:
						case END_FILE:
							popXPath();
							break;
						default:
							break;
						}
						process(event, dc);
						if ( event.isUnit() ) popXPath();
					}
				}
				catch ( Throwable e ) {
					System.out.println("ERROR (" + e.getClass().getName()+")");
					System.out.println(e.getLocalizedMessage());
					if ( showTrace ) e.printStackTrace();
				}
				finally {
					closeOutput();
				}
			}
		}
	}
	
	private void popXPath () {
		xpath = xpath.substring(0, xpath.lastIndexOf('/'));
	}
	
	private void openOutput (File file) {
		try {
			xpath = "";
			writer = new PrintWriter(new File(file.getAbsolutePath()+".txt"));
		}
		catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void closeOutput () {
		if ( writer != null ) {
			writer.close();
			writer = null;
		}
	}

	private void process (Event event,
		String dc)
	{
		Object obj = event.getResource();
		if ( obj == null ) return;
		
		switch ( event.getType() ) {
		case START_XLIFF:
			processXliff(event);
			break;
		case START_FILE:
			processFile(event);
			break;
		case START_GROUP:
			processGroup(event);
			break;
		case TEXT_UNIT:
			processUnit(event);
			break;
		default:
			// Do nothing
			break;
		}
	}
	
	private void print (String text) {
		if ( consoleOutput ) {
			System.out.print(text);
		}
		writer.print(text);
	}
	
	private String yesOrNo (boolean value) {
		return value ? "yes" : "no";
	}
	
	private String preserveOrDefault (boolean value) {
		return value ? "preserve" : "default";
	}
	
	private void processXliff (Event event) {
		print(xpath);
		switch ( dc ) {
		case DataCategories.TRANSLATE:
			print("\ttranslate=yes\n");
			break;
		case DataCategories.PRESERVESPACE:
			print("\txml:space="+preserveOrDefault(event.getStartXliffData().getPreserveWS())+"\n");
			break;
		default:
			print("\t\n");
		}
	}

	private void processFile (Event event) {
		StartFileData sfd = event.getStartFileData();
		print(xpath);
		switch ( dc ) {
		case DataCategories.TRANSLATE:
			print("\ttranslate="+yesOrNo(sfd.getTranslate())+"\n");
			break;
		case DataCategories.PRESERVESPACE:
			print("\txml:space="+preserveOrDefault(sfd.getPreserveWS())+"\n");
			break;
		default:
			print("\t\n");
		}
	}

	private void processGroup (Event event) {
		StartGroupData sgd = event.getStartGroupData();
		print(xpath);
		switch ( dc ) {
		case DataCategories.TRANSLATE:
			print("\ttranslate="+yesOrNo(sgd.getTranslate())+"\n");
			break;
		case DataCategories.PRESERVESPACE:
			print("\txml:space="+preserveOrDefault(sgd.getPreserveWS())+"\n");
			break;
		default:
			print("\t\n");
		}
	}
	
	private void processUnit (Event event) {
		Unit unit = event.getUnit();
		switch ( dc ) {
		case DataCategories.TRANSLATE:
			print(xpath+"\ttranslate="+yesOrNo(unit.getTranslate())+"\n");
			break;
		case DataCategories.PRESERVESPACE:
			print(xpath+"\txml:space="+preserveOrDefault(unit.getPreserveWS())+"\n");
			break;
		default:
			print(xpath);
			display((IWithITSAttributes)unit, null);
			break;
		}
		
		URIContext uc = event.getURIContext();
		String fragIdRoot = uc.getFragmentId();
		int segmentCount = 0;
		int ignorableCount = 0;
		for ( Part part : unit ) {
			// Update the path
			if ( part.isSegment() ) {
				xpath += "/segment["+(++segmentCount)+"]";
			}
			else {
				xpath += "/ignorable["+(++ignorableCount)+"]";
			}
			// Process the part
			processContent(part.getSource(), false, fragIdRoot, unit.getTranslate(), unit.getPreserveWS());
			processContent(part.getTarget(), true, fragIdRoot, unit.getTranslate(), unit.getPreserveWS());
			popXPath();
		}
	}

	private void processContent (Fragment frag,
		boolean isTarget,
		String fragIdRoot,
		boolean translate,
		boolean preserveWS)
	{
		Stack<Info> info = new Stack<>();
		info.push(new Info("", translate, preserveWS));
		int offset = 0;
		
		String xp = xpath + (isTarget ? "/target[1]" : "/source[1]");
		if ( frag == null ) return;
		for ( Object obj : frag ) {
			if ( obj instanceof String ) {
				// Normalize the text (without trimming of leading/trailing)
				String text = ((String)obj).replaceAll("\\s{2,}", " ");
				// Output
				print(xp+"/#"+offset+","+(offset+=text.length()));
				switch ( dc ) {
				case DataCategories.TRANSLATE:
					print("\ttranslate="+yesOrNo(info.peek().trans)+"\n");
					break;
				case DataCategories.PRESERVESPACE:
					print("\txml:space="+preserveOrDefault(info.peek().pws)+"\n");
					break;
				default:
					print("\t\n");
				}
			}
			else if ( obj instanceof MTag ) {
				MTag mtag = (MTag)obj;
				String prefix = "/{START:";
				switch ( mtag.getTagType() ) {
				case OPENING:
					if ( mtag.getTranslate() != null ) {
						info.push(new Info(mtag.getId(), mtag.getTranslate(), info.peek().pws));
					}
					else {
						info.push(new Info(mtag.getId(), info.peek().trans, info.peek().pws));
					}
					break;
				case CLOSING:
					// Find the opening in the stack 
					for ( int j=0; j<info.size(); j++ ) {
						if ( info.get(j).id.equals(mtag.getId()) ) {
							info.remove(j);
							break;
						}
					}
					prefix = "/{END:";
					break;
				case STANDALONE:
					prefix = "/{LONE:";
					break;
				}
				
				if ( mtag.hasITSItem()
					|| dc.equals(DataCategories.TRANSLATE)
					|| dc.equals(DataCategories.PRESERVESPACE) )
				{
					String fragId = fragIdRoot+"/"+(isTarget ? "t=" : "")+mtag.getId();
					print(xp+prefix+fragId+"}");
					display(mtag, info);
				}
			}
		}
	}
	
	private void display (IWithITSAttributes its,
		Stack<Info> info)
	{
		print("\t");
		if ( dc.equals(DataCategories.TRANSLATE) ) {
			print("translate="+yesOrNo(info.peek().trans)+"\n");
			return;
		}
		if ( dc.equals(DataCategories.PRESERVESPACE) ) {
			print("xml:space="+preserveOrDefault(info.peek().pws)+"\n");
			return;
		}
		if ( its.hasITSItem() ) {
			ITSItems items = its.getITSItems();
			IITSItem item = items.get(dc);
			if ( item == null ) {
				print("\n");
				return;
			}
			
			String ar = item.getAnnotatorRef();
			print("ar="+(ar==null ? "" : item.getAnnotatorRef()));

			switch ( dc ) {
			case DataCategories.MTCONFIDENCE:
				MTConfidence mtc = (MTConfidence)item;
				print(" confidence="+mtc.getMtConfidence());
				break;
			case DataCategories.DOMAIN:
				Domain dom = (Domain)item;
				print(" domains="+dom.getDomain());
				break;
			case DataCategories.ALLOWEDCHARACTERS:
				AllowedCharacters ac = (AllowedCharacters)item;
				print(" allowedCharacters="+ac.getAllowedCharacters());
				break;
			case DataCategories.TEXTANALYSIS:
				TextAnalysis ta = (TextAnalysis)item;
				print(" classRef="+ta.getTaClassRef());
				print(" ident="+ta.getTaIdent());
				print(" identRef="+ta.getTaIdentRef());
				print(" source="+ta.getTaSource());
				print(" confidence="+ta.getTaConfidence());
				break;
			}
		}
		print("\n");
	}
}

/* ITS locations
 * translate = file, group, unit, mrk/sm
 * xml:space = xliff, file, group, unit, mrk/sm (its:space)
 * mt-confidence = mrk/sm, mtc:match
 */
